package com.padgett.resourcemanagement.data;

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceConfig;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.DatastoreTimeoutException;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.SortDirection;
import com.google.appengine.api.datastore.ReadPolicy;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.users.User;
import com.padgett.resourcemanagement.listener.InvoiceServletContext;

/**
 * Datastore DAO that provides methods for UI to persist data into GAE Datastore.
 * @author andyleung
 *
 */
public final class ClientDAO {
	private static final Logger logger=Logger.getLogger(ClientDAO.class.getName());
	private static final int MOST_RECENT_PERSONAL_CLIENTS=25;
	private static final int MOST_RECENT_BUSINESS_CLIENTS=25;
	/**
	 * If key is provided, either it's null or invalid key, it will create a brand
	 * new entity.  This method is applicable to both business client and personal
	 * client.
	 * @param key
	 * @param client
	 * @return
	 */
	public static Key saveEntity(Entity entity){
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Transaction t = datastore.beginTransaction();
		Key savedKey=null;
		try{
			savedKey=datastore.put(entity);
			t.commit();
			MemcacheService cache = MemcacheServiceFactory.getMemcacheService();
			cache.put(KeyFactory.keyToString(savedKey), entity);
		}catch(DatastoreTimeoutException dte){
			logger.severe(dte.getMessage());
		}catch(ConcurrentModificationException cme){
			logger.severe(cme.getMessage());
		}catch(DatastoreFailureException cfe){
			logger.severe(cfe.getMessage());
		}catch(Exception e){
			logger.severe(e.getMessage());
		}finally{
			if(t.isActive()){
				t.rollback();
			}
		}
		return savedKey;
	}
	/**
	 * Specific method that saves invoice first, then update invoice number.
	 * @param key
	 * @param client
	 * @return
	 */
	public static Key saveInvoice(Entity invoice){
		//DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		AsyncDatastoreService datastore=DatastoreServiceFactory.getAsyncDatastoreService();
		Future<Transaction> t = datastore.beginTransaction();
		Future<Key> savedKey=null;
		Key entityKey=null;
		try{
			savedKey=datastore.put(invoice);
			t.get().commit();
			Query query=new Query("Invoice")
			.setFilter(new Query.FilterPredicate("CreatedDate",Query.FilterOperator.LESS_THAN,invoice.getProperty("CreatedDate")))
			.addSort("CreatedDate", SortDirection.DESCENDING);
			PreparedQuery preparedQuery=datastore.prepare(query);
			
			//100 should be safe to find out next Invoice Number.
			List<Entity> entities=preparedQuery.asList(FetchOptions.Builder.withLimit(100));
			int counter=0;
			if(entities.isEmpty()){
				// Use default
				invoice.setProperty("InvoiceNumber", InvoiceServletContext.INVOICE_START_NUMBER);
			}else{
				for(Entity entity:entities){
					counter++;
					long invoiceNumber=((Long)entity.getProperty("InvoiceNumber")).longValue();
					if(invoiceNumber!=-1){
						// The last updated number.  Use counter to update current.
						invoice.setProperty("InvoiceNumber", invoiceNumber+counter);
						break;
					}
				}
			}
			// Second phase commit
			t=datastore.beginTransaction();
			savedKey=datastore.put(invoice);
			entityKey=savedKey.get();
			t.get().commit();
		}catch(DatastoreTimeoutException dte){
			dte.printStackTrace();
			logger.severe(dte.getMessage());
		}catch(ConcurrentModificationException cme){
			cme.printStackTrace();
			logger.severe(cme.getMessage());
		}catch(DatastoreFailureException cfe){
			cfe.printStackTrace();
			logger.severe(cfe.getMessage());
		}catch(Exception e){
			e.printStackTrace();
			logger.severe(e.getMessage());
		}finally{
		}
		return entityKey;
	}
	public static boolean deleteEntity(Key key){
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Transaction t = datastore.beginTransaction();
		boolean deleted=true;
		try{
			datastore.delete(key);
			t.commit();
		}catch(IllegalArgumentException iae){
			logger.severe(iae.getMessage());
			iae.printStackTrace();
			deleted=false;
		}finally{
			if(t.isActive()){
				t.rollback();
			}
			MemcacheService cache = MemcacheServiceFactory.getMemcacheService();
			if(cache.get(KeyFactory.keyToString(key))!=null){
				cache.delete(KeyFactory.keyToString(key));
			}
		}
		return deleted;
	}
	/**
	 * Leverage memcache to load entity faster and not overly abuse quota.
	 * @param key
	 * @return
	 */
	public static Entity loadEntity(Key key){
		Entity entity=null;
		MemcacheService cache = MemcacheServiceFactory.getMemcacheService();
		if((entity=(Entity)cache.get(KeyFactory.keyToString(key)))==null){
			DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
			Transaction t = datastore.beginTransaction();
			try{
				entity=datastore.get(key);
				cache.put(KeyFactory.keyToString(entity.getKey()), entity);
			}catch(EntityNotFoundException enfe){
				logger.severe(enfe.getMessage());
				enfe.printStackTrace();
			}finally{
				if(t.isActive()){
					t.rollback();
				}
			}
		}
		return entity;
	}
	public static Future<Entity> asyncLoadEntity(Key key){
		AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();
		Future<Entity> entity=datastore.get(key);
		return entity;
	}
	public static List<Entity> getAllClients(){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("Client").addSort("CreatedDate", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
		entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static Map<Key,Entity> getClientsByKeys(List<Key> clientKeys){
		Map<Key,Entity> clients=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try{
			clients=datastore.get(clientKeys);
		}catch(IllegalArgumentException iae){
			iae.printStackTrace();
			clients=new HashMap<Key,Entity>();
		}
		return clients;
	}
	public static List<Entity> getAllUsers(){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("User").addSort("Email", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static Entity getUserByGoogleAppsUserId(String googleAppsUserId){
		Entity entity=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("User");
		query.setFilter(new Query.FilterPredicate("Email", FilterOperator.EQUAL, googleAppsUserId));
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entity=preparedQuery.asSingleEntity();
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
		}
		return entity;
	}
	public static List<Entity> getClients(boolean active,boolean business,boolean personal){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter businessFilter=new Query.FilterPredicate("Type", FilterOperator.EQUAL, (business==true?"Business":""));
		Filter personalFilter=new Query.FilterPredicate("Type", FilterOperator.EQUAL, (personal==true?"Personal":""));
		Filter filter=null;
		if(business&&personal){
			filter=CompositeFilterOperator.or(businessFilter,personalFilter);
		}else{
			if(business==true&&personal==false){
				filter=businessFilter;
			}else{
				filter=personalFilter;
			}
		}
		
		Query query=new Query("Client")
		.setFilter(filter)
		.addSort("LastName", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
		entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	/**
	 * Get all personal clients sorted by last name.
	 * @return
	 */
	public static List<Entity> getPersonalClients(){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("PersonalClient")
		.addSort("LastName", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	/**
	 * Returns 25 most recent created personal clients.
	 * @return
	 */
	public static List<Entity> getRecentPersonalClients(){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("PersonalClient")
		.addSort("CreatedDate", SortDirection.DESCENDING)
		.addSort("LastName", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults().limit(MOST_RECENT_PERSONAL_CLIENTS));
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	/**
	 * Returns 25 most recent created business clients.
	 * @return
	 */
	public static List<Entity> getRecentBusinessClients(){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("BusinessClient")
		.addSort("CreatedDate", SortDirection.DESCENDING)
		.addSort("LegalName", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults().limit(MOST_RECENT_BUSINESS_CLIENTS));
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getAllServices(boolean completed,boolean incomplete,boolean billable,boolean nonBillable){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter allFilters=null;
		Filter completeComposite=null;
		Filter completeFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.TRUE);
		Filter incompleteFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.FALSE);
		if(completed&&incomplete){
			completeComposite=CompositeFilterOperator.or(completeFilter,incompleteFilter);
		}else{
			if(completed&&!incomplete){
				completeComposite=completeFilter;
			}else if(!completed&&incomplete){
				completeComposite=incompleteFilter;
			}else{
				completeComposite=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, "");
			}
		}

		Filter billableComposite=null;
		Filter billableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.TRUE);
		Filter nonBillableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.FALSE);
		if(billable&&nonBillable){
			billableComposite=CompositeFilterOperator.or(billableFilter,nonBillableFilter);
		}else{
			if(billable&&!nonBillable){
				billableComposite=billableFilter;
			}else if(!billable&&nonBillable){
				billableComposite=nonBillableFilter;
			}else{
				billableComposite=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, "");
			}
		}
		allFilters=CompositeFilterOperator.and(completeComposite,billableComposite);
		Query query=new Query("Service")
		.setFilter(allFilters)
		.addSort("Completed", SortDirection.ASCENDING)
		.addSort("DueDate", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults().limit(100));
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	/**
	 * 
	 * @param month 0 as January so there is no translation from java.util.Date
	 * @return
	 */
	public static List<Entity> getAllServicesDuedByMonth(int month){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter filter=CompositeFilterOperator.or(new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.TRUE),new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.FALSE));
		filter=CompositeFilterOperator.and(filter,new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.TRUE));
		GregorianCalendar startDate=new GregorianCalendar();
		startDate.setTime(new Date());
		startDate.set(GregorianCalendar.DAY_OF_MONTH, 1);
		startDate.set(GregorianCalendar.MONTH, month);
		GregorianCalendar endDate=new GregorianCalendar();
		endDate.setTime(new Date());
		endDate.set(GregorianCalendar.MONTH, month);
		endDate.set(GregorianCalendar.DAY_OF_MONTH, endDate.getActualMaximum(GregorianCalendar.DAY_OF_MONTH));
		Filter dateFilter=CompositeFilterOperator.and(new Query.FilterPredicate("DueDate", FilterOperator.GREATER_THAN_OR_EQUAL, startDate.getTime()),
				new Query.FilterPredicate("DueDate", FilterOperator.LESS_THAN_OR_EQUAL, endDate.getTime()));
		filter=CompositeFilterOperator.and(dateFilter,filter);
		Query query=new Query("Service")
		.setFilter(filter)
		.addSort("DueDate", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getMyServices(User user,boolean completed,boolean incomplete,boolean billable,boolean nonBillable){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter allFilters=null;
		Filter completeComposite=null;
		Filter userFilter=new Query.FilterPredicate("Accountant", FilterOperator.EQUAL, user.getEmail());
		Filter completeFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.TRUE);
		Filter incompleteFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.FALSE);
		if(completed&&incomplete){
			completeComposite=CompositeFilterOperator.or(completeFilter,incompleteFilter);
		}else{
			if(completed&&!incomplete){
				completeComposite=completeFilter;
			}else if(!completed&&incomplete){
				completeComposite=incompleteFilter;
			}else{
				completeComposite=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, "");
			}
		}

		Filter billableComposite=null;
		Filter billableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.TRUE);
		Filter nonBillableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.FALSE);
		if(billable&&nonBillable){
			billableComposite=CompositeFilterOperator.or(billableFilter,nonBillableFilter);
		}else{
			if(billable&&!nonBillable){
				billableComposite=billableFilter;
			}else if(!billable&&nonBillable){
				billableComposite=nonBillableFilter;
			}else{
				billableComposite=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, "");
			}
		}
		allFilters=CompositeFilterOperator.and(completeComposite,billableComposite);
		Query query=new Query("Service")
		.setFilter(CompositeFilterOperator.and(userFilter,allFilters))
		.addSort("Completed", SortDirection.ASCENDING)
		.addSort("DueDate", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults().limit(100));
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getAllUserServices(User userId,boolean completed,boolean incomplete,boolean billable,boolean nonBillable){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter allFilters=null;
		Filter completeComposite=null;
		Filter completeFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.TRUE);
		Filter incompleteFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.FALSE);
		if(completed&&incomplete){
			completeComposite=CompositeFilterOperator.or(completeFilter,incompleteFilter);
		}else{
			if(completed&&!incomplete){
				completeComposite=completeFilter;
			}else if(!completed&&incomplete){
				completeComposite=incompleteFilter;
			}else{
				completeComposite=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, "");
			}
		}

		Filter billableComposite=null;
		Filter billableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.TRUE);
		Filter nonBillableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.FALSE);
		if(billable&&nonBillable){
			billableComposite=CompositeFilterOperator.or(billableFilter,nonBillableFilter);
		}else{
			if(billable&&!nonBillable){
				billableComposite=billableFilter;
			}else if(!billable&&nonBillable){
				billableComposite=nonBillableFilter;
			}else{
				billableComposite=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, "");
			}
		}
		allFilters=CompositeFilterOperator.and(completeComposite,billableComposite);
		Query query=new Query("Service")
		.setFilter(allFilters)
		.addSort("Completed", SortDirection.ASCENDING)
		.addSort("DueDate", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults().limit(100));
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getAllClientServices(Key clientId,boolean completed,boolean incomplete,boolean billable,boolean nonBillable){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter allFilters=null;
		Filter completeComposite=null;
		Filter completeFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.TRUE);
		Filter incompleteFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.FALSE);
		if(completed&&incomplete){
			completeComposite=CompositeFilterOperator.or(completeFilter,incompleteFilter);
		}else{
			if(completed&&!incomplete){
				completeComposite=completeFilter;
			}else if(!completed&&incomplete){
				completeComposite=incompleteFilter;
			}else{
				completeComposite=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, "");
			}
		}

		Filter billableComposite=null;
		Filter billableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.TRUE);
		Filter nonBillableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.FALSE);
		if(billable&&nonBillable){
			billableComposite=CompositeFilterOperator.or(billableFilter,nonBillableFilter);
		}else{
			if(billable&&!nonBillable){
				billableComposite=billableFilter;
			}else if(!billable&&nonBillable){
				billableComposite=nonBillableFilter;
			}else{
				billableComposite=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, "");
			}
		}
		allFilters=CompositeFilterOperator.and(completeComposite,billableComposite);
		Query query=new Query("Service")
		.setAncestor(clientId)
		.setFilter(allFilters)
		.addSort("Completed", SortDirection.ASCENDING)
		.addSort("DueDate", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getMyClientServices(User user,Key clientId,boolean completed,boolean incomplete,boolean billable,boolean nonBillable){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter allFilters=null;
		Filter completeComposite=null;
		Filter userFilter=new Query.FilterPredicate("Accountant", FilterOperator.EQUAL, user.getEmail());
		Filter completeFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.TRUE);
		Filter incompleteFilter=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, Boolean.FALSE);
		if(completed&&incomplete){
			completeComposite=CompositeFilterOperator.or(completeFilter,incompleteFilter);
		}else{
			if(completed&&!incomplete){
				completeComposite=completeFilter;
			}else if(!completed&&incomplete){
				completeComposite=incompleteFilter;
			}else{
				completeComposite=new Query.FilterPredicate("Completed", FilterOperator.EQUAL, "");
			}
		}

		Filter billableComposite=null;
		Filter billableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.TRUE);
		Filter nonBillableFilter=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, Boolean.FALSE);
		if(billable&&nonBillable){
			billableComposite=CompositeFilterOperator.or(billableFilter,nonBillableFilter);
		}else{
			if(billable&&!nonBillable){
				billableComposite=billableFilter;
			}else if(!billable&&nonBillable){
				billableComposite=nonBillableFilter;
			}else{
				billableComposite=new Query.FilterPredicate("Billable", FilterOperator.EQUAL, "");
			}
		}
		allFilters=CompositeFilterOperator.and(completeComposite,billableComposite);
		Query query=new Query("Service")
		.setAncestor(clientId)
		.setFilter(CompositeFilterOperator.and(userFilter,allFilters))
		.addSort("Completed", SortDirection.ASCENDING)
		.addSort("DueDate", SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	/**
	 * This returns only the unbilled services.  If services are added to invoice, it will marked as billed and if it is
	 * removed from invoice, it will be marked as unbilled.
	 * @param clientId
	 * @return
	 */
	public static List<Entity> getClientCompletedUnbilledServices(Key clientId){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		try{
			Filter unbilledFilter=new Query.FilterPredicate("Billed", FilterOperator.EQUAL, Boolean.FALSE);
			Filter completedFilter=new Query.FilterPredicate("Completed",FilterOperator.EQUAL,Boolean.TRUE);
			Query query=new Query("Service")
			.setAncestor(clientId)
			.setFilter(CompositeFilterOperator.and(unbilledFilter,completedFilter))
			.addSort("DueDate", SortDirection.ASCENDING);
			PreparedQuery preparedQuery=datastore.prepare(query);
				entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Key> saveClientServices(List<Entity> services){
		List<Key> keys=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Transaction t=datastore.beginTransaction();
		try{
			keys=datastore.put(services);
			t.commit();
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			keys=new ArrayList<Key>();
		}finally{
			if(t.isActive()){
				t.rollback();
			}
		}
		return keys;
	}
	public static String getNextServiceId(){
		String serviceId=null;
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Transaction t=datastore.beginTransaction();
		try{
			Query query=new Query("ServiceId")
			.setFilter(new Query.FilterPredicate("Used",Query.FilterOperator.EQUAL,Boolean.FALSE)).addSort("ServiceId", SortDirection.DESCENDING);
			PreparedQuery preparedQuery=datastore.prepare(query);
			entities=preparedQuery.asList(FetchOptions.Builder.withLimit(5));
			if(entities!=null&&!entities.isEmpty()){
				// Mark first entity as used and return it.
				Entity service=entities.get(0);
				service.setProperty("Used",Boolean.TRUE);
				serviceId=(String)service.getProperty("serviceId");
				t.commit();
			}else{
				//TODO Urgent, immediately generate 5000 Service Ids because schedule task may not work or service ids used up faster than planned.
			}
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
		}finally{
			if(t.isActive()){
				t.rollback();
			}
		}
		return serviceId;
	}
	public static int getNextInvoiceNumber(){
		int invoiceNumber=0;
		List<Entity> entities=null;
		double deadline = 5.0;

		// Construct a read policy for eventual consistency
		ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.STRONG);

		// Set both the read policy and the call deadline
		DatastoreServiceConfig datastoreConfig =
		      DatastoreServiceConfig.Builder.withReadPolicy(policy).deadline(deadline);

		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig);
		Transaction t=datastore.beginTransaction();
		try{
			Query query=new Query("InvoiceNumber")
			.setFilter(new Query.FilterPredicate("Used",Query.FilterOperator.EQUAL,Boolean.FALSE)).addSort("Number", SortDirection.ASCENDING);
			PreparedQuery preparedQuery=datastore.prepare(query);
			entities=preparedQuery.asList(FetchOptions.Builder.withLimit(1));
			if(entities!=null&&!entities.isEmpty()){
				// Mark first entity as used and return it.
				Entity invoiceNum=entities.get(0);
				if(((Boolean)invoiceNum.getProperty("Used"))==Boolean.FALSE){
					invoiceNum.setProperty("Used",Boolean.TRUE);
					datastore.put(invoiceNum);
					t.commit();
				}else{
					System.out.println("Last Minute detection");
				}
				invoiceNumber=((Long)(invoiceNum.getProperty("Number"))).intValue();
			}else{
				//TODO Urgent, immediately generate 500 Invoice Number
			}
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
		}finally{
			if(t.isActive()){
				System.out.println("Rolling back");
				t.rollback();
			}
		}
		return invoiceNumber;
	}
	public static List<Entity> getInvoiceLineItems(Key invoiceId){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("InvoiceLineItem")
		.setAncestor(invoiceId);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getInvoiceLineItemsByServices(Key serviceKey){
		List<Entity> invoiceLineItems=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter serviceFilter=new Query.FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.EQUAL, serviceKey);
		Query query=new Query("InvoiceLineItem")
		.setFilter(serviceFilter);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			invoiceLineItems=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			invoiceLineItems=new ArrayList<Entity>();
		}
		return invoiceLineItems;
	}
	public static List<Entity> getAllInvoices(boolean paid,boolean unpaid){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter paidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.TRUE);
		Filter unpaidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.FALSE);
		Filter filter=null;
		if(paid&&unpaid){
			filter=CompositeFilterOperator.or(paidFilter,unpaidFilter);
		}else{
			if(paid==true&&unpaid==false){
				filter=paidFilter;
			}else{
				filter=unpaidFilter;
			}
		}
		Query query=new Query("Invoice")
		.setFilter(filter)
		.addSort("FullyPaid",SortDirection.ASCENDING)
		.addSort("CreatedDate",SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults().limit(100));
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getMyInvoices(User user,boolean paid,boolean unpaid){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter userFilter=new Query.FilterPredicate("Accountant", FilterOperator.EQUAL, user.getEmail());
		Filter paidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.TRUE);
		Filter unpaidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.FALSE);
		Filter filter=null;
		if(paid&&unpaid){
			filter=CompositeFilterOperator.or(paidFilter,unpaidFilter);
		}else{
			if(paid==true&&unpaid==false){
				filter=paidFilter;
			}else{
				filter=unpaidFilter;
			}
		}
		Query query=new Query("Invoice")
		.setFilter(CompositeFilterOperator.and(userFilter,filter))
		.addSort("FullyPaid",SortDirection.ASCENDING)
		.addSort("CreatedDate",SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults().limit(100));
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getAllClientInvoices(Key clientId,boolean paid,boolean unpaid){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter paidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.TRUE);
		Filter unpaidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.FALSE);
		Filter filter=null;
		if(paid&&unpaid){
			filter=CompositeFilterOperator.or(paidFilter,unpaidFilter);
		}else{
			if(paid==true&&unpaid==false){
				filter=paidFilter;
			}else{
				filter=unpaidFilter;
			}
		}
		Query query=new Query("Invoice")
		.setAncestor(clientId)
		.setFilter(filter)
		.addSort("FullyPaid",SortDirection.ASCENDING)
		.addSort("CreatedDate",SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getMyClientInvoices(User user,Key clientId,boolean paid,boolean unpaid){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Filter userFilter=new Query.FilterPredicate("Accountant", FilterOperator.EQUAL, user.getEmail());
		Filter paidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.TRUE);
		Filter unpaidFilter=new Query.FilterPredicate("FullyPaid", FilterOperator.EQUAL, Boolean.FALSE);
		Filter filter=null;
		if(paid&&unpaid){
			filter=CompositeFilterOperator.or(paidFilter,unpaidFilter);
		}else{
			if(paid==true&&unpaid==false){
				filter=paidFilter;
			}else{
				filter=unpaidFilter;
			}
		}
		Query query=new Query("Invoice")
		.setAncestor(clientId)
		.setFilter(CompositeFilterOperator.and(userFilter,filter))
		.addSort("FullyPaid",SortDirection.ASCENDING)
		.addSort("CreatedDate",SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	public static List<Entity> getInvoiceReceipts(Key invoiceId){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("InvoiceReceipt")
		.setAncestor(invoiceId);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
	
	/**
	 * Report - Return all personal clients with "AllFiled" false for Detail Report.
	 */
	public static List<Entity> getIncompletedFamily(){
		List<Entity> entities=null;
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		Query query=new Query("PersonalClient")
		.setFilter(new Query.FilterPredicate("AllFiled", FilterOperator.EQUAL, Boolean.FALSE))
		.addSort("DateIn", SortDirection.ASCENDING)
		.addSort("Accountant",SortDirection.ASCENDING);
		PreparedQuery preparedQuery=datastore.prepare(query);
		try{
			entities=preparedQuery.asList(FetchOptions.Builder.withDefaults());
		}catch(Exception e){
			logger.severe(e.getMessage());
			e.printStackTrace();
			entities=new ArrayList<Entity>();
		}
		return entities;
	}
}
